<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>微信年度聊天报告</title>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts@latest/dist/echarts.min.js"></script>
    <script type="text/javascript" src="https://assets.pyecharts.org/assets/v5/echarts-wordcloud.min.js"></script>
    <style>
        /* --- 全局样式重置与基础设定 --- */
        :root {
            --primary-color: #07c160; /* 微信绿 */
            --primary-color-light: #e6f8ee; /* 浅微信绿，用于背景点缀 */
            --accent-color: #4a90e2; /* 辅助色，用于背景光晕 */
            --text-color: #1a1a1a; /* 更深的文本色，对比更强 */
            --light-text-color: #555;
            --bg-color: #f7f7f7;
            --card-bg-color: rgba(255, 255, 255, 0.85); /* 卡片半透明，以透出背景 */
            --border-color: #e8e8e8;
            --shadow: 0 8px 32px rgba(0, 0, 0, 0.1); /* 更柔和、更弥散的阴影 */
            --border-radius: 16px; /* 更大的圆角，更显柔和 */
        }
    
        /* ✨ 新增：平滑滚动行为 */
        html {
            scroll-behavior: smooth;
        }
    
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
            margin: 0;
            background-color: var(--bg-color);
            color: var(--text-color);
            display: flex;
            justify-content: center;
            padding: 40px 20px; /* 增加上下内边距 */
            line-height: 1.6; /* 优化行高，提升阅读舒适度 */
            overflow-x: hidden; /* 防止背景元素导致横向滚动条 */
    
            /* ✨ 核心美化：优雅的渐变背景和装饰性光晕 */
            background-image: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
            position: relative;
        }
    
        /* ✨ 新增：背景装饰光晕效果 */
        body::before, body::after {
            content: '';
            position: fixed;
            border-radius: 50%;
            opacity: 0.2;
            filter: blur(100px);
            z-index: -1;
        }
    
        body::before {
            width: 400px;
            height: 400px;
            background: var(--primary-color-light);
            top: -150px;
            left: -150px;
        }
    
        body::after {
            width: 500px;
            height: 500px;
            background: var(--accent-color);
            bottom: -200px;
            right: -200px;
        }
    
        /* --- 主容器 --- */
        .container {
            width: 100%;
            max-width: 1200px;
            display: flex;
            flex-direction: column;
            gap: 32px; /* 增大间距 */
        }
    
        /* --- 卡片基础样式 --- */
        .card {
            background-color: var(--card-bg-color);
            border-radius: var(--border-radius);
            box-shadow: var(--shadow);
            padding: 32px; /* 增大内边距 */
            border: 1px solid rgba(255, 255, 255, 0.5); /* 玻璃拟态边框 */
            backdrop-filter: blur(10px); /* ✨ 新增：玻璃拟态效果 */
            -webkit-backdrop-filter: blur(10px);
            transition: transform 0.3s ease, box-shadow 0.3s ease; /* ✨ 优化：更平滑的过渡 */
            
            /* ✨ 新增：入场动画 */
            opacity: 0;
            transform: translateY(20px);
            animation: card-fade-in 0.6s ease forwards;
        }
    
        /* ✨ 新增：卡片悬浮效果 */
        .card:hover {
            transform: translateY(-5px);
            box-shadow: 0 16px 40px rgba(0, 0, 0, 0.12);
        }
        
        /* ✨ 新增：为每个卡片设置不同的动画延迟 */
        .header-card { animation-delay: 0.1s; }
        .filter-card { animation-delay: 0.2s; }
        .card.full-width:nth-of-type(1) { animation-delay: 0.3s; }
        .section-header { animation-delay: 0.4s; }
        /* 调整后续动画延迟 */
        .highlights-card { animation-delay: 0.5s; } 
        .chart-grid .card:nth-child(1) { animation-delay: 0.6s; }
        .chart-grid .card:nth-child(2) { animation-delay: 0.7s; }
        .card.full-width:nth-of-type(2) { animation-delay: 0.8s; }
        .card.full-width:nth-of-type(3) { animation-delay: 0.9s; }
        .card.full-width:nth-of-type(4) { animation-delay: 1.0s; }
        
        /* --- 段落标题 --- */
        .section-header {
            font-size: 2em;
            font-weight: 700;
            color: var(--text-color);
            padding-bottom: 10px;
            border-bottom: 3px solid var(--primary-color);
            margin: 16px 0 -16px 0; /* 调整外边距以适应新布局 */
            
            /* ✨ 新增：入场动画 */
            opacity: 0;
            transform: translateY(20px);
            animation: card-fade-in 0.6s ease forwards;
        }
    
        /* --- 顶部介绍卡片 --- */
        .header-card {
            text-align: center;
        }
    
        .header-card h1 {
            font-size: 3em;
            font-weight: 800;
            color: var(--text-color);
            margin: 0 0 10px;
            letter-spacing: 1px; /* 增加字间距 */
        }
    
        .header-card p {
            font-size: 1.2em;
            color: var(--light-text-color);
            margin: 5px 0;
        }
        
        #first_time {
            font-weight: bold;
            color: var(--primary-color);
            font-size: 1.4em;
        }
    
        .timer-container {
            display: flex;
            justify-content: center;
            gap: 20px;
            margin-top: 25px;
        }
    
        /* ✨ 美化：计时器样式 */
        .timer-box {
            background: linear-gradient(145deg, #e6e6e6, #ffffff);
            box-shadow: 5px 5px 10px #d4d4d4, -5px -5px 10px #ffffff;
            padding: 15px 20px;
            border-radius: 12px;
            font-size: 1.6em;
            font-weight: 700;
            min-width: 110px;
            text-align: center;
            color: var(--text-color);
            transition: transform 0.2s ease;
        }
        .timer-box:hover {
            transform: scale(1.05);
        }
    
        /* --- 年份筛选器 --- */
        .filter-card {
            display: flex;
            align-items: center;
            gap: 20px;
            flex-wrap: wrap;
        }
        .filter-card h2 {
            margin: 0;
            font-size: 1.4em;
            flex-shrink: 0;
        }
        .filter-buttons {
            display: flex;
            flex-wrap: wrap;
            gap: 12px;
        }
        .filter-btn {
            padding: 10px 20px;
            border: 1px solid var(--border-color);
            background: var(--card-bg-color);
            color: var(--text-color);
            border-radius: 30px;
            cursor: pointer;
            transition: all 0.3s ease;
            font-weight: 500;
        }
        .filter-btn:hover {
            border-color: var(--primary-color);
            color: var(--primary-color);
            transform: translateY(-2px);
        }
        .filter-btn.active {
            background-color: var(--primary-color);
            color: #fff;
            font-weight: bold;
            border-color: var(--primary-color);
            box-shadow: 0 4px 10px rgba(7, 193, 96, 0.3);
        }
    
        /* --- 图表网格布局 --- */
        .chart-grid {
            display: grid;
            grid-template-columns: repeat(2, 1fr);
            gap: 32px; /* 增大间距 */
        }
    
        .chart-card {
            display: flex;
            flex-direction: column;
        }
        
        .chart-title-container {
            display: flex;
            justify-content: center;
            align-items: center;
            gap: 15px;
            margin-bottom: 10px;
        }
        
        .chart-card h2 {
            margin: 0;
            text-align: center;
            font-size: 1.6em;
            font-weight: 600;
        }
        
        .chart-subtitle {
            text-align: center;
            color: var(--light-text-color);
            margin: -5px 0 20px 0;
            font-size: 1em;
        }
        
        .chart-toggle-btn {
            padding: 6px 14px;
            font-size: 0.9em;
            border: 1px solid var(--border-color);
            border-radius: 15px;
            cursor: pointer;
            background-color: #f5f5f5;
            transition: all 0.3s ease;
        }
        .chart-toggle-btn.active {
            background-color: var(--primary-color);
            color: white;
            border-color: var(--primary-color);
            box-shadow: 0 2px 6px rgba(7, 193, 96, 0.2);
        }
        
        .full-width {
            grid-column: 1 / -1;
        }
        
        .chart-container {
            width: 100%;
            height: 450px;
            flex-grow: 1;
        }

        /* --- 【新增】数据亮点卡片样式 --- */
        .highlights-card {
            display: flex;
            justify-content: space-around;
            align-items: center;
            flex-wrap: wrap;
            gap: 20px;
            padding: 25px;
        }
        .highlight-item {
            text-align: center;
        }
        .highlight-item .value {
            font-size: 2.5em;
            font-weight: 700;
            color: var(--primary-color);
            line-height: 1.2;
        }
        .highlight-item .label {
            font-size: 1em;
            color: var(--light-text-color);
        }
        .highlight-item .date {
            font-size: 0.9em;
            color: var(--light-text-color);
        }

        
        /* ✨ 新增：卡片淡入动画 */
        @keyframes card-fade-in {
            from {
                opacity: 0;
                transform: translateY(20px);
            }
            to {
                opacity: 1;
                transform: translateY(0);
            }
        }
        
        /* 响应式设计 */
        @media (max-width: 992px) {
            .chart-grid { grid-template-columns: 1fr; }
        }
        @media (max-width: 768px) {
            body { padding: 20px 10px; }
            .container { gap: 20px; }
            .card { padding: 20px; }
            .header-card h1 { font-size: 2.2em; }
            .timer-box { font-size: 1.2em; min-width: 70px; padding: 12px 10px; }
            .filter-card { flex-direction: column; align-items: flex-start; }
            .chart-title-container { flex-direction: column; gap: 8px; }
            .chart-card h2 { font-size: 1.4em; }
            .highlight-item .value { font-size: 2em; }
        }
    </style>
</head>
<body>

<main class="container">
    <!-- 顶部介绍卡片 -->
    <section class="card header-card">
        <h1>{{my_nickname}} & {{ta_nickname}}</h1>
        <p>我们的故事开始于</p>
        <p id="first_time" class="first-time">{{first_time}}</p>
        <p>这是我们相识的</p>
        <div class="timer-container">
            <span id="t_d" class="timer-box">... 天</span>
            <span id="t_h" class="timer-box">... 时</span>
            <span id="t_m" class="timer-box">... 分</span>
            <span id="t_s" class="timer-box">... 秒</span>
        </div>
    </section>

    <!-- 年份筛选器 -->
    <section class="card filter-card">
        <h2>查看报告年份:</h2>
        <div id="year-filters" class="filter-buttons"></div>
    </section>

    <!-- 图表区域 -->
    <div class="card chart-card full-width">
        <div class="chart-title-container">
            <h2>每月消息趋势</h2>
            <div id="month-chart-toggle">
                <button class="chart-toggle-btn active" onclick="switchMonthChartType('line')">折线图</button>
                <button class="chart-toggle-btn" onclick="switchMonthChartType('bar')">柱状图</button>
            </div>
        </div>
        <p class="chart-subtitle">记录了每个月我们互发消息的数量 (点击月份可查看当月日历)</p>
        <div id="echarts-month_count" class="chart-container"></div>
    </div>
    
    <h2 class="section-header">数据洞察</h2>

    <!-- 【新增】数据亮点卡片 -->
    <section class="card highlights-card">
        <div class="highlight-item">
            <div id="longest-streak" class="value">--</div>
            <div class="label">最长连续聊天天数</div>
        </div>
        <div class="highlight-item">
            <div id="peak-day-count" class="value">--</div>
            <div class="label">单日消息量峰值</div>
            <div id="peak-day-date" class="date"></div>
        </div>
    </section>

    <section class="chart-grid">
        <div class="card chart-card">
            <h2>谁更爱发言 💬</h2>
            <p class="chart-subtitle">我们各自发送的消息数量对比</p>
            <div id="echarts-sender" class="chart-container"></div>
        </div>

        <div class="card chart-card">
            <h2>我们聊了些什么 🥰</h2>
            <p class="chart-subtitle">消息类型与表情包统计</p>
            <div id="echarts-types" class="chart-container"></div>
        </div>
    </section>
    
    <div class="card chart-card full-width">
        <h2>一周聊天热度 🔥</h2>
        <p class="chart-subtitle">看看我们周一到周日，哪天聊得最多</p>
        <div id="echarts-weekday" class="chart-container"></div>
    </div>
    
    <div class="card chart-card full-width">
        <h2>年度热词</h2>
        <p class="chart-subtitle">我们最常说的那些词</p>
        <div id="echarts-wordcloud" class="chart-container" style="height: 600px;"></div>
    </div>

    <div id="calendar-wrapper" class="card chart-card full-width">
        <h2 id="calendar-title">聊天全纪录</h2>
        <p class="chart-subtitle">深色代表当天聊得火热，浅色则表示轻声问候</p>
        <div id="echarts-calendar" class="chart-container" style="height: 300px;"></div>
    </div>
</main>


<script type="text/javascript">
    // --- 动态计时器 ---
    function getRTime() {
        var tt = document.getElementById("first_time").innerText;
        // 兼容 YYYY-MM-DD HH:mm:ss 格式
        var formattedTime = tt.replace(/-/g, "/");
        var EndTime = new Date(formattedTime);
        if (isNaN(EndTime.getTime())) return;

        var NowTime = new Date();
        var t = NowTime.getTime() - EndTime.getTime();
        var d = Math.floor(t / 1000 / 60 / 60 / 24);
        var h = Math.floor(t / 1000 / 60 / 60 % 24);
        var m = Math.floor(t / 1000 / 60 % 60);
        var s = Math.floor(t / 1000 % 60);
        document.getElementById("t_d").innerHTML = d + " 天";
        document.getElementById("t_h").innerHTML = h + " 时";
        document.getElementById("t_m").innerHTML = m + " 分";
        document.getElementById("t_s").innerHTML = s + " 秒";
    }
    setInterval(getRTime, 1000);
    getRTime();

</script>
<script>
    // --- ECharts 逻辑 ---
    const wxid = '{{wxid}}';
    const chartElements = {
        month_count: echarts.init(document.getElementById('echarts-month_count')),
        sender: echarts.init(document.getElementById('echarts-sender')),
        types: echarts.init(document.getElementById('echarts-types')),
        weekday: echarts.init(document.getElementById('echarts-weekday')),
        wordcloud: echarts.init(document.getElementById('echarts-wordcloud')),
        calendar: echarts.init(document.getElementById('echarts-calendar')),
    };
    
    let lastMonthCountOption = null;
    /** @type {Array<[string, number]>} */
    let currentCalendarData = []; 

    function showLoading() {
        for (const key in chartElements) {
            chartElements[key].showLoading({ text: '正在加载数据...' });
        }
    }
    
    function hideLoading() {
        for (const key in chartElements) {
            chartElements[key].hideLoading();
        }
    }
    
    function applyChartOption(chartInstance, jsonData, chartType) {
        const option = JSON.parse(jsonData);
        option.toolbox = { show: false };

        if (chartType === 'sender' || chartType === 'types') {
            if (option.legend) {
                option.legend.type = 'scroll';
                option.legend.orient = 'horizontal';
                option.legend.left = 'center';
                option.legend.top = 'bottom';
            }
            if (option.series) {
                option.series.forEach(s => {
                    if (s.type === 'pie') {
                        s.center = ['50%', '45%'];
                    }
                    if (s.label) {
                        s.label.overflow = 'breakAll'; 
                        s.label.width = 70; 
                    }
                });
            }
        }
        
        chartInstance.setOption(option, true);
        return option;
    }
    
    function fetchMonthCount(time_range) {
        return fetch('/month_count', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({ wxid, time_range })
        })
        .then(response => response.json())
        .then(data => {
            lastMonthCountOption = applyChartOption(chartElements.month_count, data.chart_data, 'month_count');
            const currentActiveType = document.querySelector('#month-chart-toggle .active').innerText.includes('折线') ? 'line' : 'bar';
            switchMonthChartType(currentActiveType, true);
        });
    }

    function fetchWordcloud(time_range) {
        return fetch('/wordcloud', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({ wxid, time_range })
        })
        .then(response => response.json())
        .then(data => {
            const option = JSON.parse(data.chart_data);
            option.series[0].sizeRange = [14, 80];
            option.toolbox = { show: false };
            chartElements.wordcloud.setOption(option, true);
        });
    }
    
    function fetchCalendar(time_range, year) {
         return fetch('/calendar', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({ wxid, time_range })
        })
        .then(response => response.json())
        .then(data => {
            const calendarTitleEl = document.getElementById('calendar-title');
            if (year && year !== 'all') {
                if (time_range[0].substring(5, 7) !== time_range[1].substring(5, 7)) {
                     calendarTitleEl.innerText = `${year}年聊天日历`;
                }
            } else {
              calendarTitleEl.innerText = '聊天全纪录';
            }
            
            const option = JSON.parse(data.chart_data);
            currentCalendarData = option.series[0].data;
            calculateAndDisplayFunFacts(currentCalendarData);
            
            // 【重要修改】使用新的方式添加标记
            addSpecialMarkersToOption(option, currentCalendarData);
            
            option.toolbox = { show: false };
            
            if (year && year !== 'all' && option.calendar && option.calendar[0]) {
                const startMonth = time_range[0].substring(0, 7);
                const endMonth = time_range[1].substring(0, 7);
                option.calendar[0].range = (startMonth === endMonth) ? startMonth : year.toString();
            }
            chartElements.calendar.setOption(option, true);
        });
    }

    function fetchMessageCounter(time_range) {
        return fetch('/message_counter', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({ wxid, time_range })
        })
        .then(response => response.json())
        .then(data => {
            applyChartOption(chartElements.sender, data.chart_data_sender, 'sender');
            applyChartOption(chartElements.types, data.chart_data_types, 'types');
            applyChartOption(chartElements.weekday, data.chart_data_weekday, 'weekday');
        });
    }

    function fetchAndInitFilters() {
        const initial_time_range = ['1997-01-01 00:00:00', '2035-01-01 00:00:00'];
        return fetch('/calendar', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({ wxid, time_range: initial_time_range })
        })
        .then(response => response.json())
        .then(data => {
            const option = JSON.parse(data.chart_data);
            if (!option.calendar || !option.calendar[0].range) {
                 console.error("无法从日历数据中获取年份范围");
                 updateAllCharts('all'); 
                 return;
            }
            const calendarRange = option.calendar[0].range;
            const startYear = parseInt(calendarRange[0]);
            const endYear = parseInt(calendarRange[1]);
            const years = Array.from({length: endYear - startYear + 1}, (_, i) => startYear + i);

            const filterContainer = document.getElementById('year-filters');
            filterContainer.innerHTML = ''; 

            const allBtn = document.createElement('button');
            allBtn.className = 'filter-btn'; // 默认不再是active
            allBtn.innerText = '全部时间';
            allBtn.onclick = () => updateAllCharts('all');
            filterContainer.appendChild(allBtn);

            const sortedYears = years.sort((a,b) => b - a); // 倒序排列
            sortedYears.forEach(year => {
                const yearBtn = document.createElement('button');
                yearBtn.className = 'filter-btn';
                yearBtn.innerText = `${year}年`;
                yearBtn.onclick = () => updateAllCharts(year);
                filterContainer.appendChild(yearBtn);
            });
            // 默认加载最近一年，并使其高亮
            const latestYear = sortedYears[0] || 'all';
            updateAllCharts(latestYear);
        })
        .catch(error => {
            console.error("初始化年份筛选器失败:", error);
            const filterContainer = document.getElementById('year-filters');
            const allBtn = document.createElement('button');
            allBtn.className = 'filter-btn active';
            allBtn.innerText = '全部时间';
            allBtn.onclick = () => updateAllCharts('all');
            filterContainer.appendChild(allBtn);
            updateAllCharts('all');
        });
    }
    
    function calculateAndDisplayFunFacts(calendarData) {
        if (!calendarData || calendarData.length === 0) {
            document.getElementById('longest-streak').innerText = '0';
            document.getElementById('peak-day-count').innerText = '0';
            document.getElementById('peak-day-date').innerText = '无记录';
            return;
        }

        let longestStreak = 0;
        let currentStreak = 0;
        if (calendarData.length > 0) {
            const sortedDates = calendarData.map(d => new Date(d[0])).sort((a, b) => a - b);
            
            if (sortedDates.length > 0) {
              longestStreak = 1;
              currentStreak = 1;
            }

            for (let i = 1; i < sortedDates.length; i++) {
                const diff = (sortedDates[i] - sortedDates[i-1]) / (1000 * 60 * 60 * 24);
                if (diff === 1) {
                    currentStreak++;
                } else if (diff > 1) {
                    longestStreak = Math.max(longestStreak, currentStreak);
                    currentStreak = 1;
                }
            }
            longestStreak = Math.max(longestStreak, currentStreak);
        }
        document.getElementById('longest-streak').innerText = longestStreak;

        const peakDay = calendarData.reduce((max, current) => current[1] > max[1] ? current : max, calendarData[0]);
        document.getElementById('peak-day-count').innerText = peakDay[1];
        document.getElementById('peak-day-date').innerText = peakDay[0];
    }
    
    /**
     * 【重要修改】使用散点图系列来添加特殊标记
     * @param {object} option - ECharts option对象
     * @param {Array<[string, number]>} calendarData - 日历图的原始数据
     */
    function addSpecialMarkersToOption(option, calendarData) {
        if (!option.series || !option.series[0] || !calendarData || calendarData.length === 0) {
            return;
        }

        const firstTimeStr = document.getElementById("first_time").innerText.split(' ')[0];
        const peakDay = calendarData.reduce((max, current) => current[1] > max[1] ? current : max, calendarData[0]);

        // 确保 series 数组存在
        if (!option.series) {
            option.series = [];
        }

        // 添加一个新的散点图系列用于标记
        option.series.push({
            type: 'scatter',
            coordinateSystem: 'calendar', // 关键：使用日历坐标系
            symbolSize: 20,
            symbol: 'pin',
            zlevel: 10, // 确保在最上层
            label: {
                show: true,
                position: 'top',
                color: '#000',
                fontSize: 12,
                fontWeight: 'bold',
                formatter: function (params) {
                    return params.name; // 显示我们在数据中定义的name
                }
            },
            data: [
                {
                    name: '低谷',
                    value: [firstTimeStr, 0], // value数组第一个是日期，第二个是任意值（不影响位置）
                    itemStyle: { color: '#e5405c' }
                },
                {
                    name: '巅峰',
                    value: [peakDay[0], peakDay[1]],
                    itemStyle: { color: '#ff9a2e' }
                }
            ]
        });
    }
    
    function handleMonthClick(params) {
        const activeYearBtn = document.querySelector('.filter-btn.active');
        const yearText = activeYearBtn ? activeYearBtn.innerText : '';
        
        if (yearText.includes('全部')) {
             alert('请先选择一个具体的年份再进行钻取哦~');
             return;
        }
        if (!yearText) return; // 如果没有选中的年份，则不执行

        const year = parseInt(yearText);
        const month = params.dataIndex + 1;
        
        const startDate = `${year}-${String(month).padStart(2, '0')}-01 00:00:00`;
        const endDate = new Date(year, month, 0);
        const lastDay = endDate.getDate();
        const endDateStr = `${year}-${String(month).padStart(2, '0')}-${String(lastDay).padStart(2, '0')} 23:59:59`;
        
        const time_range = [startDate, endDateStr];
        
        const calendarTitleEl = document.getElementById('calendar-title');
        calendarTitleEl.innerText = `${year}年${month}月聊天日历`;
        
        chartElements.calendar.showLoading();
        fetchCalendar(time_range, year).finally(() => {
            chartElements.calendar.hideLoading();
            document.getElementById('calendar-wrapper').scrollIntoView({ behavior: 'smooth' });
        });
    }

    function switchMonthChartType(type, forceUpdate = false) {
        if (!lastMonthCountOption) return;
        
        if (!forceUpdate) {
            document.querySelectorAll('#month-chart-toggle .chart-toggle-btn').forEach(btn => {
                btn.classList.toggle('active', btn.innerText.includes(type === 'line' ? '折线' : '柱状'));
            });
        }
        
        lastMonthCountOption.series.forEach(s => s.type = type);
        chartElements.month_count.setOption(lastMonthCountOption, true);
    }

    function updateAllCharts(year) {
        showLoading();

        document.querySelectorAll('.filter-btn').forEach(btn => {
            const btnText = btn.innerText;
            btn.classList.toggle('active', (year === 'all' && btnText === '全部时间') || btnText === `${year}年`);
        });

        const time_range = (year === 'all')
            ? ['1997-01-01 00:00:00', '2035-01-01 00:00:00']
            : [`${year}-01-01 00:00:00`, `${year}-12-31 23:59:59`];

        Promise.all([
            fetchMessageCounter(time_range),
            fetchMonthCount(time_range),
            fetchWordcloud(time_range),
            fetchCalendar(time_range, year)
        ]).finally(() => {
            hideLoading();
        });
    }

    window.addEventListener('resize', () => {
        for (const key in chartElements) {
            chartElements[key].resize();
        }
    });

    document.addEventListener('DOMContentLoaded', () => {
        chartElements.month_count.on('click', handleMonthClick);
        fetchAndInitFilters();
    });
</script>
</body>
</html>